﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;

namespace XTrace
{
	public partial class XTraceSource
	{
		readonly List<XTraceSourcesBundle> _bundles = new List<XTraceSourcesBundle>();
		public IEnumerable<XTraceSourcesBundle> Bundles
		{
			get
			{
				lock (_bundles)
				{
					return _bundles.ToArray();
				}
			}
		}

		/// <summary>
		/// Create bundle from listener
		/// </summary>
		/// <param name="listener"></param>
		public void AddBundle(XTraceListener listener)
		{
			var oneToOne = new XTraceSourcesBundle();
			oneToOne.AddListener(listener);
			AddBundle(oneToOne);
		}

		public void AddBundle(XTraceSourcesBundle bundle)
		{
			lock (_bundles)
			{
				if (!_bundles.Contains(bundle))
				{
					_bundles.Add(bundle);
					bundle.AddSource(this);
				}
			}
		}

		#region Hide object members

		/// <summary />
		[EditorBrowsable(EditorBrowsableState.Never)]
		public override bool Equals(object obj)
		{
			return base.Equals(obj);
		}

		/// <summary />
		[EditorBrowsable(EditorBrowsableState.Never)]
		public override int GetHashCode()
		{
			return base.GetHashCode();
		}

		/// <summary />
		[EditorBrowsable(EditorBrowsableState.Never)]
		public override string ToString()
		{
			return base.ToString();
		}

		/// <summary />
		[EditorBrowsable(EditorBrowsableState.Never)]
		public new Type GetType()
		{
			return base.GetType();
		}

		/// <summary>Do not call this method.</summary>
		[SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "b")]
		[SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "a")]
		[Obsolete("Do not call this method", true)]
		[EditorBrowsable(EditorBrowsableState.Never)]
		public new static bool Equals(object a, object b)
		{
			return object.Equals(a, b);
		}

		/// <summary>Do not call this method.</summary>
		[SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "b")]
		[SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "a")]
		[Obsolete("Do not call this method", true)]
		[EditorBrowsable(EditorBrowsableState.Never)]
		public new static bool ReferenceEquals(object a, object b)
		{
			return object.ReferenceEquals(a, b);
		}

		#endregion

		protected void Notify(XTraceEvent traceEvent)
		{
			foreach (var bundle in _bundles)
			{
				foreach (var listener in bundle.Listeners)
				{
					listener.Event(traceEvent);
				}
			}
			// else // - excellent sample of how it works in another languages. for-else construction
			if (!_bundles.Any())
			{
				NotifyAllStatic(traceEvent);
			}
		}

		internal static string GetEventFromStack(Exception exception)
		{
#if PocketPC
			return XTraceEvent.PocketPCunknownData;
#else
			var stack = new StackTrace(exception, true);

			var goodFrame = GetGoodFrame(stack);
			var meth = goodFrame.GetMethod();
			return meth.DeclaringType.Name + "." + meth.Name;
			// Dynamic numbers is inappropriate in EventId. Line number available in corresponding column.
			//+ " (at " + goodFrame.GetFileLineNumber() + ")";
#endif
		}

#if !PocketPC
		internal static StackTrace GetGoodStack()
		{
			var stack = new StackTrace();
			int i;
			GetGoodFrame(stack, out i);
			return new StackTrace(i, true);
		}

		internal static StackFrame GetGoodFrame()
		{
			return GetGoodFrame(new StackTrace(true));
		}

		internal static StackFrame GetGoodFrame(StackTrace stack)
		{
			int index;
			return GetGoodFrame(stack, out index);
		}

		internal static StackFrame GetGoodFrame(StackTrace stack, out int index)
		{
			StackFrame goodFrame = null;
			index = 0;
			var frames = stack.GetFrames();
			var xtraceAsm = typeof(XTraceSource).Assembly;
			for (int i = 0, c = frames.Length; i < c; i++)
			{
				var frame = frames[i];
				var asm = frame.GetMethod().DeclaringType.Assembly;

				if (asm != xtraceAsm)
				{
					goodFrame = frame;
					index = i;
					break;
				}
			}
			return goodFrame;
		}

#endif

	}
}